package com.fanrui.code.threadgroup;

import com.fanrui.code.thread.FrameTask;
import com.fanrui.code.thread.MainLoopPlus;
import com.fanrui.code.thread.MainLoopPlus.LogExceptionRunnable;
import com.fanrui.code.util.logger.Stdout;

/**
 * 一组MainLoop，其中每一个都为一批固定的Player服务；同时确保一个Player始终使用同一个MainLoop。（参考netty的channel和eventLoop的关系：一个loop为多个channel服务，一个channel始终使用同一个loop）
 * 
 *
 */
public class GroupedMainLoopPlus<ENTITY_ID_TYPE> {

	//
	// 按entityId（PlayerId）分组，给不同的MainLoop执行Task
	//

	public GroupingAlgorithm<ENTITY_ID_TYPE> algorithm;

	public GroupedMainLoopPlus(String name, GroupingAlgorithm<ENTITY_ID_TYPE> ga) {
		algorithm = ga;
		MainLoopPlus[] loops = new MainLoopPlus[algorithm.getGroupSize()];
		for (int i = 0; i < loops.length; i++) {
			loops[i] = new MainLoopPlus(name + i, 1); // 这里一定用单一线程。
			GroupDeterminer<ENTITY_ID_TYPE> determiner = new GroupDeterminer<ENTITY_ID_TYPE>(algorithm, i);
			loops[i].execute(new LogExceptionRunnable() {
				@Override
				public void logExceptionRun() {
					determiners.set(determiner);
				}
			}); // 只能用这种方法让 真正执行MainLoop的线程和对应的GroupDeterminer绑定。
		}
		mainLoops = loops;

	}

	private MainLoopPlus[] mainLoops;

	public MainLoopPlus get(ENTITY_ID_TYPE entityId) {
		return mainLoops[algorithm.getGroup(entityId)];
	}

	//
	// 提供判断某entityId（PlayerId）是否属于当前MainLoop的方法。
	// ［用于类似帧循环这样的对全体Player进行的业务逻辑］
	//
	private ThreadLocal<GroupDeterminer<ENTITY_ID_TYPE>> determiners = new ThreadLocal<GroupDeterminer<ENTITY_ID_TYPE>>();

	/**
	 * 判断某个entity（Player），是否应该在当前线程（MainLoop）中执行。 注意，只能在GroupedMainLoopPlus的MainLoop中调用才有效！
	 * 
	 * @deprecated 不建议在业务代码里直接使用，建议在框架代码里通过调用getGroupDeterminer()获得当前线程对应GroupDeterminer，并将GroupDeterminer作为业务方法的一个参数传递到业务代码中去（从而在业务代码中消除直接对GroupedMainLoopPlus的使用）
	 * @param entityId
	 * @return
	 */
	@Deprecated
	public boolean isInGroup(ENTITY_ID_TYPE entityId) {
		return getGroupDeterminer().isInGroup(entityId);
	}

	/**
	 * 获得当前MainLoop线程对应的GroupDeterminer
	 * 
	 * @return
	 */
	public GroupDeterminer<ENTITY_ID_TYPE> getGroupDeterminer() {
		return determiners.get();
	}

	//
	// 提供一些方法，用来发给所有MainLoop。
	//
	public void stopAndWait() {
		for (MainLoopPlus loop : mainLoops) {
			try {
				loop.stopAndWait();
			} catch (InterruptedException e) {
				Stdout.GET().error(String.format("停止主循环%s失败！", loop.toString()), e);
			}
		}
	}

	/**
	 * 注意，应该在task的run方法内部，调用GroupedMainLoopPlus.isInGroup(PlayerID)方法／或者 group.isInGroup(PlayerID) 来判断是否执行业务。 例如：
	 * 
	 * <pre>
	 * MapServerMainLoop.getInstance().addFrameLogicTask(new FrameTask(true,1, 1, TimeUnit.SECONDS) {
	 *   protected void run(int spendTime, int waitTime, int detlaTime) {
	 *     GroupDeterminer<Long> group = MapServerMainLoop.getInstance().getGroupDeterminer();
	 *     allPlayerFrameLogic(group);
	 *   }
	 * });
	 * 
	 * ...
	 * 
	 * public void allPlayerFrameLogic(GroupDeterminer group) {
	 *   for (Player player : allPlayers) { 
	 *     if (!group.isInGroup(player.getPlayerId())) {
	 *       continue; // 不对应在当前线程中，跳过
	 *     } 
	 *     player.doFrameLogic(); //对应在当前线程中，执行业务逻辑
	 *     ...
	 *   }
	 * }
	 * </pre>
	 * 
	 * @param task
	 */
	public void groupAddFrameLogicTask(FrameTask task) {
		for (int i = 0; i < mainLoops.length; i++) {
			mainLoops[i].addFrameLogicTask(task.clone()); // 注意这里必须克隆一个新实例，否则有多线程问题！
		}
	}

	/**
	 * 注意，通常情况下绝不需要使用此方法。（应使用get(playerId).execute();）
	 * 
	 * @deprecated 请考虑是不是应该使用 get(playerId).execute();
	 * @param runnable
	 */
	@Deprecated
	public void groupExecute(LogExceptionRunnable runnable) { //
		for (int i = 0; i < mainLoops.length; i++) {
			mainLoops[i].execute(runnable);
		}
	}

}
